package com.dahuyou.tryy.too.simulate.interpreter;

import com.alibaba.fastjson.JSON;
import com.dahuyou.tryy.too.simulate.interpreter.instructions.Factory;
import com.dahuyou.tryy.too.simulate.interpreter.instructions.base.BytecodeReader;
import com.dahuyou.tryy.too.simulate.interpreter.instructions.base.Instruction;
import com.dahuyou.tryy.too.simulate.interpreter.parse.clazz.clazzfile.MemberInfo;
import com.dahuyou.tryy.too.simulate.interpreter.parse.clazz.clazzfile.attributeinfo.impl.CodeAttribute;
import com.dahuyou.tryy.too.simulate.interpreter.runtime.area.Frame;
import com.dahuyou.tryy.too.simulate.interpreter.runtime.area.Thread;
import com.dahuyou.tryy.too.simulate.interpreter.runtime.area.heap.methodarea.Method;

/**
 * 解释器，负责基于指令码来执行具体程序
 */
public class Interpreter {

    /**
     * 解释器构造函数，直接就开始干活了！！！
     *
     * @param method
     */
    public Interpreter(Method method, boolean logInst) {
        Thread thread = new Thread();
        Frame frame = thread.newFrame(method);
        thread.pushFrame(frame);
        loop(thread, logInst);
    }
    private void loop(Thread thread, boolean logInst) {
        BytecodeReader reader = new BytecodeReader();
        while (true) {
            Frame frame = thread.currentFrame();
            int pc = frame.nextPC();
            thread.setPC(pc);

            reader.reset(frame.method().code, pc);
            byte opcode = reader.readByte();
            Instruction inst = Factory.newInstruction(opcode);
            if (null == inst) {
                System.out.println("Unsupported opcode " + byteToHexString(new byte[]{opcode}));
                break;
            }
            inst.fetchOperands(reader);
            frame.setNextPC(reader.pc());

            if (logInst) {
                logInstruction(frame, inst, opcode);
            }

            //exec
            inst.execute(frame);

            if (thread.isStackEmpty()) {
                break;
            }
        }
    }
    private static void logInstruction(Frame frame, Instruction inst, byte opcode) {
        Method method = frame.method();
        String className = method.clazz().name();
        String methodName = method.name();
        String outStr = (className + "." + methodName + "() \t") +
                "寄存器(指令)：" + byteToHexString(new byte[]{opcode}) + " -> " + inst.getClass().getSimpleName() + " => 局部变量表：" + JSON.toJSONString(frame.localVars().getSlots()) + " 操作数栈：" + JSON.toJSONString(frame.operandStack().getSlots());
        System.out.println(outStr);
    }

    private void loop(Thread thread, byte[] byteCode) {
        Frame frame = thread.popFrame();
        // 字节码读取器，负责维护程序计数器，以及读取指令码
        BytecodeReader reader = new BytecodeReader();
        while (true) {
            // 获取当前的程序计数器位置，并读取该位置的指令码
            int pc = frame.nextPC();
            // 设置程序计数器位置
            thread.setPC(pc);
            // 充值字节码读取器的字节码信息和位置信息
            reader.reset(byteCode, pc);
            // 获取指令码
            byte opcode = reader.readByte();
            // 根据指令码获取对应的执行指令的指令类
            Instruction inst = Factory.newInstruction(opcode);
            if (null == inst) {
                System.out.println("寄存器(指令)尚未实现 " + byteToHexString(new byte[]{opcode}));
                break;
            }
            // 获取操作数，比如IADD指令就需要从操作数的栈顶获取2个操作数，但并不是所有的指令都需要该操作
            // 比如iconst_0就不需要
            inst.fetchOperands(reader);
            // 设置程序计数器到方法栈帧中
            frame.setNextPC(reader.pc());
            System.out.println("寄存器(指令)：" + byteToHexString(new byte[]{opcode}) + " -> " + inst.getClass().getSimpleName() + " => 局部变量表：" + JSON.toJSONString(frame.localVars().getSlots()) + " 操作数栈：" + JSON.toJSONString(frame.operandStack().getSlots()));
            // 真正的执行指令！！！
            inst.execute(frame);
        }

    }

    private static String byteToHexString(byte[] codes) {
        StringBuilder sb = new StringBuilder();
        sb.append("0x");
        for (byte b : codes) {
            int value = b & 0xFF;
            String strHex = Integer.toHexString(value);
            if (strHex.length() < 2) {
                strHex = "0" + strHex;
            }
            sb.append(strHex);
        }
        return sb.toString();
    }


}
